package xj.graph2d.app;

import static xj.app.BaseApplicationFrame.getResourceString;
import static xj.app.BaseApplicationFrame.getSystemConfigFile;
import static xj.app.BaseApplicationFrame.createSplashScreen;
import static xj.app.BaseApplicationFrame.hideSplashScreen;
import static xj.app.BaseApplicationFrame.logInfo;
import static xj.app.BaseApplicationFrame.logError;
import static xj.app.BaseApplicationFrame.setSplashScreenProgress;
import static xj.app.BaseApplicationFrame.startApp;
import static xj.app.BaseApplicationFrame.initGUI;
import static xj.graph2d.DocumentConstants.DEFAULT_FILE_TYPE;
import static xj.graph2d.DocumentConstants.SERIALIZATION_FILE_TYPE;
import static xj.graph2d.DocumentConstants.UNKNOWN_FILE_TYPE;
import static xj.graph2d.DocumentConstants.XML_FILE_TYPE;
import static xj.graph2d.DocumentConstants.XML_STENCIL_FILE_TYPE;

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.filechooser.FileFilter;

import xj.app.AutoSaveListener;
import xj.app.BaseApplicationFrame;
import xj.app.DrawToolkit;
import xj.app.LookAndFeelManager;
import xj.graph2d.ArrowAttr;
import xj.graph2d.BoxGroup;
import xj.graph2d.CompoundDrawShape;
import xj.graph2d.Document;
import xj.graph2d.DocumentChangeListener;
import xj.graph2d.DocumentConstants;
import xj.graph2d.DocumentException;
import xj.graph2d.DrawAttr;
import xj.graph2d.DrawCanvas;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.DrawShapeFactory;
import xj.graph2d.EllipseShape;
import xj.graph2d.Encoder;
import xj.graph2d.GraphDocument;
import xj.graph2d.Group;
import xj.graph2d.LineShape;
import xj.graph2d.RectangleShape;
import xj.graph2d.RectangularGroup;
import xj.graph2d.SimpleDrawDocument;
import xj.graph2d.SimpleDrawView;
import xj.graph2d.TextShape;
import xj.graph2d.UndoManager;
import xj.graph2d.View;
import xj.graph2d.ViewOptions;
import xj.graph2d.app.project.NewProjectItemListener;
import xj.graph2d.app.project.NewStencilListener;
import xj.graph2d.app.project.Project;
import xj.graph2d.app.project.ProjectException;
import xj.graph2d.app.project.ProjectFolder;
import xj.graph2d.app.project.ProjectItem;
import xj.graph2d.app.project.ProjectListener;
import xj.graph2d.app.project.ProjectManager;
import xj.graph2d.app.project.ProjectType;
import xj.graph2d.app.prop.DrawShapePropertyPane;
import xj.graph2d.app.prop.ImageShapeListener;
import xj.graph2d.app.prop.ModelPropertiesPane;
import xj.graph2d.app.prop.TabbedAttributePane;
import xj.graph2d.device.InputDevice;
import xj.graph2d.shapes.CircleShape;
import xj.graph2d.shapes.CompartmentalRectangleShape;
import xj.graph2d.shapes.CompartmentalRoundRectangleShape;
import xj.graph2d.shapes.CubicCurveShape;
import xj.graph2d.shapes.DisjointGroup;
import xj.graph2d.shapes.ImageShape;
import xj.graph2d.shapes.OrthogonalLineShape;
import xj.graph2d.shapes.PolyCurveShape;
import xj.graph2d.shapes.PolyLineShape;
import xj.graph2d.shapes.PolygonShape;
import xj.graph2d.shapes.QuadCurveShape;
import xj.graph2d.shapes.RegularPolygonShape;
import xj.graph2d.shapes.RotatableCompoundDrawShape;
import xj.graph2d.shapes.RoundRectangleShape;
import xj.graph2d.shapes.ScalableCompoundDrawShape;
import xj.graph2d.shapes.ScribbleShape;
import xj.graph2d.shapes.TitledShape;
import xj.graph2d.template.Template;
import xj.graph2d.tools.CircleDrawTool;
import xj.graph2d.tools.CurveTool;
import xj.graph2d.tools.GroupTool;
import xj.graph2d.tools.HoverPopupManager;
import xj.graph2d.tools.MultiPointTool;
import xj.graph2d.tools.OnePointShapeTool;
import xj.graph2d.tools.PolyShapeDrawTool;
import xj.graph2d.tools.ScribbleTool;
import xj.graph2d.tools.TextTool;
import xj.graph2d.tools.TwoEndsDrawTool;
import xj.graph2d.tree.DocumentTreePane;
import xj.graph2d.tree.GraphDocumentTreePane;
import xj.graph2d.tree.ProjectPane;
import xj.graph2d.tree.TabbedDocumentTreePanel;
import xj.graph2d.tree.TabbedProjectPanel;
import xj.meta.ModelRepository;
import xj.meta.RootElement;
import xj.meta.RootElementImpl;
import xj.model.uml.UMLModel;
import xj.util.StringUtils;
import xj.util.gui.ExtensionFileFilter;
import xj.util.gui.Rectangle2DFloat;
import xj.util.gui.JPanelPopup;
import xj.graph2d.app.project.BreadcrumbAdapter;

public class DrawApp 
  extends Main 
  implements AutoSaveListener {

  public DrawApp(BaseApplicationFrame frame) {
    super(frame, "DrawApp");

    initDocView(); // this must be done first
    initToolkit();
     
    setSplashScreenProgress(30);

    String filename = "Untitled" + (untitledCount++);
    setCurrentFilename(filename);
    doc.setFilename(filename);
    doc.setName(filename);

    runnerManager = new DocumentRunnerManager(); 

    ConfigHelper.setSystemConfigFile(getSystemConfigFile());
    ConfigHelper configHelper = new ConfigHelper(this);
    configHelper.processSystemConfig();

    userProfile = new UserProfile(this); 
    userProfile.processUserConfig();

    setSplashScreenProgress(60);

    useTabbedPane = !isSingleDocumentMode();

    view.setAlignment(SwingConstants.LEFT);

    DrawShapeFactory factory = DrawShapeFactory.getInstance();
    factory.setIconMap(icons);

    // set up command managers
    editManager = new EditManager(this);
    JMenuItem undoMenuItem = menus.get("Undo");
    JMenuItem redoMenuItem = menus.get("Redo");
    undoChangeListener = new UndoChangeListener(undoMenuItem, redoMenuItem);
    undoChangeListener.setMain(this);
    UndoManager um = doc.getUndoManager();
    if (um != null) {
      um.setChangeListener(undoChangeListener);
    }

    recentlyOpenedFilesMenu = (JMenu) menus.get("OpenRecent");
    Object act = actions.get("OpenRecentFile");
    if (act instanceof OpenRecentFileListener) {
      recentlyOpenedFilesAction = (OpenRecentFileListener) act;
    }

    setSplashScreenProgress(70);

    // set up views
    float cwidth = canvasWidth > 0 ? canvasWidth : Document.DEFAULT_CANVAS_WIDTH;
    float cheight = canvasHeight > 0 ? canvasHeight : Document.DEFAULT_CANVAS_HEIGHT;
    view.setVirtualCanvasRectangle(new Rectangle2D.Float(0, 0, cwidth, cheight));
			
    displayMessage(getResourceString("System") + " version " + 
		   getResourceString("Version"));

    if (debug) {
      if (view != null) {
	Rectangle2D r = view.getVirtualCanvasRectangle();
	System.out.println("Virtual canvas=" + r);
	System.out.println("Physical canvas=" + view.virtualToPhysical(r));
	System.out.println("Viewport=" + view.getViewportRectangle());
      }
    }

    toolbar = getToolBar("mainToolbar");    

    hoverPopupManager = new HoverPopupManager(this);

    makeViewOptionPanel();
    makeLocationLabel();
    createStatusBar();
    setUpContentPane(view, useTabbedPane);

    setSplashScreenProgress(75);

    loadInitialProjects();

    setSplashScreenProgress(99);
    
    setFrameTitle();

    startAutoSave(this);
    //startBlinker();
  }

  /*
   * Accessors
   */
  public SimpleDrawDocument getDoc() {
    return doc;
  }

  public SimpleDrawView getView() {
    return view;
  }

  public ProjectManager getProjectManager() {
    return projectManager;
  }

  public HoverPopupManager getHoverPopupManager() {
    return hoverPopupManager;
  }

  public DocumentRunnerManager getDocumentRunnerManager() {  
    return runnerManager; 
  }

  public int getCanvasWidth() {
    return canvasWidth;
  }

  public int getCanvasHeight() {
    return canvasHeight;
  }

  public static DrawAction getDrawAction(String name) {
    if (name != null) { 
      return actions.get(name);
    }
    return null;
  }

  public static ActionListener getAction(String name) {
    if (name != null) {
      Object act = actions.get(name);
      if (act instanceof ActionListener) {
	return (ActionListener) act;
      }
    }
    return null;
  }

  public Template getTemplate(String name) {
    if (name != null) { 
      return templates.get(name);
    }
    return null;
  }

  public ToolPanel getCurrentToolPanel() { 
    if (templatePanel != null) { 
      return templatePanel.getCurrentToolPanel();
    }
    return null;
  }

  public Template getCurrentTemplate() { 
    if (templatePanel != null) { 
      ToolPanel toolPanel = templatePanel.getCurrentToolPanel();
      if (toolPanel != null) { 
	String tempName = toolPanel.getTemplateName();
	return templates.get(tempName);
      }      
    }
    return null;
  }

  public JComponent getToolBar(String name) {
    if (name != null) {
      return toolbars.get(name);
    }
    return null;
  }

  public JComponent findToolButton(String tbname, String command) { 
    if (tbname != null && command != null) {
      JComponent toolbar = toolbars.get(tbname);
      if (toolbar != null) { 
	int n = toolbar.getComponentCount();
	for (int i = 0; i < n; i++) { 
	  Component c = toolbar.getComponent(i);
	  if (c instanceof AbstractButton) {
	    AbstractButton b = (AbstractButton) c;
	    if (command.equals(b.getActionCommand())) { 
	      return b;
	    }
	  }
	}
      }    
    }
    return null;
  }

  public JMenuItem getMenuItem(String name) { 
    return menus.get(name);
  }

  public void addEncoder(Encoder enc) {
    if (enc != null) {
      String name = enc.getName();
      if (name != null) {
	encoderMap.put(name, enc);
      }
    }
  }

  public Encoder getEncoder(String name) {
    if (name != null) {
      return encoderMap.get(name);
    }
    return null;
  }

  public Iterator<Encoder> getAllEncoders() {
    Collection<Encoder> c = encoderMap.values();
    return c.iterator();
  }

  public void setEncoderView(Encoder enc) {
    if (enc != null) {
      enc.setView(view);
    }
  }

  public void addPlugIn(String name, PlugIn plugin) {
    if (name != null && 
	plugin != null) {
      plugInMap.put(name, plugin);
    }
  }

  public PlugIn getPlugIn(String name) {
    if (name != null) {
      return plugInMap.get(name);
    }
    return null;
  }

  public Iterator<PlugIn> getAllPlugIns() {
    Collection<PlugIn> c = plugInMap.values();
    return c.iterator();
  }

  public DocumentChangeListener getDocumentChangeListener() {
    return docChangeListener;
  }

  public DrawShapePropertyPane getDrawShapePropertyPane() {
    return propPane;
  }

  public ModelPropertiesPane getModelPropertiesPane() { 
    return modelPropPane;
  }

  public TabbedAttributePane getTabbedAttributePane() { 
    return tabbedPropPane;
  }

  public void updateShapeVisualProperties() {
    if (propPane != null) {
      propPane.updateAttributes();
    }
  }

  public void showShapeProperties(DrawShape shape) {
    if (SwingUtilities.isEventDispatchThread()) { 
      if (propPane != null) {
	if (!(shape instanceof CompoundDrawShape)) {
	  propPane.show(shape);
	} else {
	  propPane.show(null);
	}
      }
      if (modelPropPane != null) {
	modelPropPane.show(shape);
      }
    }
  }

  public boolean isUseStandardFontsOnly() {
    return useStandardFontsOnly;
  }

  public void setUseStandardFontsOnly(boolean useStandardFontsOnly) {
    this.useStandardFontsOnly = useStandardFontsOnly;
  }

  public boolean isShowRuler() {
    return showRuler;
  }

  public void setShowRuler(boolean showRuler) {
    this.showRuler = showRuler;
    if (view != null) {
      view.setShowRuler(showRuler);
    }
  }

  public DrawAttr getDrawAttr() {
    return drawattr;
  }

  public TemplatePanel getTemplatePanel() {
    return templatePanel;
  }

  public DrawCanvas getCanvas() {
    return view.getCanvas();
  }

  public JTree getProjectTree() { 
    return projectPane.getProjectTree();
  }

  public String getCurrentProjectItemName() { 
    if (projectManager != null) { 
      ProjectItem cur = projectManager.getCurrentItem();
      if (cur != null) {
	return cur.getFullName(); 
      }
    }
    return null;
  }

  /*
   * Auxiliary methods
   */
  protected void initDocView() {
    doc = new DrawAppDocument(this);
    view = new DrawAppView(this, doc);

    view.setShowRuler(showRuler);
  }

  protected void initDocView(SimpleDrawDocument doc) {
    if (doc != null) { 
      this.doc = doc;
      view = doc.makeView();
      view.setShowRuler(showRuler);    
    } else { 
      initDocView();
    }
  } 

  protected void setDocView(SimpleDrawDocument doc, SimpleDrawView view) {
    this.doc = doc;
    this.view = view;
    if (doc != null) {
      currentFilename = doc.getFilename();
      currentFiletype = doc.getFiletype();

      UndoManager um = doc.getUndoManager();
      if (um != null) {
	um.setChangeListener(undoChangeListener);
	um.postChangeEvent();
      }

      editManager.resetDocView();

    }

  }

  public void initProjectModel(Project project) {
    if (project != null) {
      String name = project.getName();
      RootElement par;
      if (project.getProjectType() == ProjectType.UML) {
	par = new UMLModel(name);
      } else {
	par = new RootElementImpl(name);
      }
      ModelRepository rep = ModelRepository.getInstance();
      rep.addModel(par);
      project.setModel(par);
    }
  }

  protected void initDrawShapeFactory(DrawShapeFactory factory) {
    if (factory != null) {
      factory.addPrototype("LineShape", new LineShape());
      factory.addPrototype("RectangleShape", new RectangleShape());
      factory.addPrototype("FilledRectangleShape", new RectangleShape(true));
      factory.addPrototype("RoundRectangleShape", new RoundRectangleShape());
      factory.addPrototype("FilledRoundRectangleShape",
			   new RoundRectangleShape(true));
      factory.addPrototype("EllipseShape", new EllipseShape());
      factory.addPrototype("FilledEllipseShape", new EllipseShape(true));
      factory.addPrototype("CircleShape", new CircleShape());
      factory.addPrototype("ScribbleShape", new ScribbleShape());
      factory.addPrototype("PolygonShape", new PolygonShape());
      factory.addPrototype("PolyLineShape", new PolyLineShape());
      factory.addPrototype("QuadCurveShape", new QuadCurveShape());
      factory.addPrototype("CubicCurveShape", new CubicCurveShape());
      factory.addPrototype("PolyCurveShape", new PolyCurveShape());
      factory.addPrototype("OrthogonalLineShape", new OrthogonalLineShape());
      factory.addPrototype("CompartmentalRectangleShape",
			   new CompartmentalRectangleShape());
      factory.addPrototype("CompartmentalRoundRectangleShape",
			   new CompartmentalRoundRectangleShape());
      factory.addPrototype("TitledShape", new TitledShape());

      TextShape t;
      factory.addPrototype("TextShape", t = new TextShape());
      t.showCursor(true);
      factory.addPrototype("TextBox", new BoxGroup());

      factory.addPrototype("ImageShape", new ImageShape());

      factory.addPrototype("RectangularShape",
			   new xj.graph2d.shapes.RectangularShape());
      factory.addPrototype("RegularPolygonShape", new RegularPolygonShape());

      factory.addPrototype("CompoundDrawShape", new CompoundDrawShape());
      factory.addPrototype("Group", new Group());
      factory.addPrototype("BoxGroup", new BoxGroup());
      factory.addPrototype("RectangularGroup", new RectangularGroup());
      factory.addPrototype("DisjointGroup", new DisjointGroup());

      factory.addPrototype("ScalableCompoundDrawShape",
			   new ScalableCompoundDrawShape());
      factory.addPrototype("RotatableCompoundDrawShape",
			   new RotatableCompoundDrawShape());

      // graph components
      factory.addPrototype("LineEdge", new LineShape(true));
      factory.addPrototype("RectNode", new RectangleShape(0, 0, 20, 30, false, true));

    }
  }

  protected void initToolkit() {
    toolkit = new DrawToolkit(512);

    DrawShapeFactory factory = DrawShapeFactory.getInstance();
    initDrawShapeFactory(factory);

    addTool("Line", 
	    new TwoEndsDrawTool((LineShape) factory.getPrototype("LineShape"), 
				DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR,
				"Line"));
    addTool("Rect", 
	    new TwoEndsDrawTool((RectangleShape) factory.getPrototype("RectangleShape"),
				DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				"Rect"));
    addTool("Oval", 
	    new TwoEndsDrawTool((EllipseShape) factory.getPrototype("EllipseShape"),
				DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				"Oval"));
    addTool("Circle", 
	    new CircleDrawTool((CircleShape) factory.getPrototype("CircleShape"),
			       DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR));
    addTool("Scribble", 
	    new ScribbleTool((ScribbleShape) factory.getPrototype("ScribbleShape"),
			     DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR));

    // addTool("Point", new PointTool());
    addTool("Point", new MultiPointTool());
    addTool("Group", new GroupTool());

    // addTool(new OnePointShapeTool(new ComponentShape(new
    // JButton("Button")), true));
    addTool("Polygon", 
	    new PolyShapeDrawTool((PolygonShape) factory.getPrototype("PolygonShape"),
				  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				  "Polygon"));
    addTool("Polyline", 
	    new PolyShapeDrawTool((PolyLineShape) factory.getPrototype("PolyLineShape"),
				  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				  "Polyline"));
    addTool("FilledRect", 
	    new TwoEndsDrawTool((RectangleShape) factory.getPrototype("FilledRectangleShape"),
				DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				"FilledRect"));
    addTool("FilledOval", 
	    new TwoEndsDrawTool((EllipseShape) factory.getPrototype("FilledEllipseShape"),
				DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				"FilledOval"));
    addTool("Text", 
	    new TextTool(DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR));
    addTool("RoundRect", 
	    new TwoEndsDrawTool((RoundRectangleShape) factory.getPrototype("RoundRectangleShape"),
				DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				"RoundRect"));
    addTool("FilledRoundRect",
	    new TwoEndsDrawTool((RoundRectangleShape) factory.getPrototype("FilledRoundRectangleShape"),
	    DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				"FilledRoundRect"));
    addTool("Arc", 
	    new CurveTool((QuadCurveShape) factory.getPrototype("QuadCurveShape"),
			  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
			  "Arc"));
    addTool("Curve", 
	    new CurveTool((CubicCurveShape) factory.getPrototype("CubicCurveShape"),
			  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
			  "Curve"));

    addTool("PolyCurve", 
	    new PolyShapeDrawTool((PolyCurveShape) factory.getPrototype("PolyCurveShape"),
				  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				  "PolyCurve"));
    addTool("OrthogonalLine", 
	    new PolyShapeDrawTool((OrthogonalLineShape) factory.getPrototype("OrthogonalLineShape"),
				  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR, 
				  "OrthogonalLine"));

    ImageShapeListener listener = new ImageShapeListener();
    listener.setCurrentDirectory(frame.getDefaultDir());
    addTool("Image", 
	    new OnePointShapeTool((ImageShape) factory.getPrototype("ImageShape"), listener,
				  DrawShapeConstants.CLONE_SYSTEM_DRAW_ATTR));

    // addTool("Compound-1", new OnePointShapeTool((CompoundDrawShape)
    // factory.getPrototype("Compound-1"), true));

    view.setDrawToolkit(toolkit);
    // toolkit.setCurrentTool("Line", view);
    toolkit.setCurrentTool("Point", view);
  }

  protected static class ProjectInfo {    
    String projName;
    ProjectType projType;
    String itemName;
    Document.DocumentType itemType;
  }

  /*
   * Initialize the project manager. Create default project and default item 
   */
  protected void initProjectManager(ProjectInfo pinfo) {
    if (pinfo != null) {
      if (debug) {
	System.out.println("DrawApp.initProjectManager(): name=" + 
			   pinfo.projName + " type=" + pinfo.projType);
      }

      projectMode = true;
      projectManager = new ProjectManager(this, pinfo.projName, pinfo.projType);      
      projectManager.setDefaultItemNameType(pinfo.itemName, pinfo.itemType);
      doc.setDocumentType(pinfo.itemType);
      ProjectItem item = new ProjectItem(doc);
      String itemName = projectManager.getNewDefaultItemName();
      item.setName(itemName);
      setCurrentFilename(doc.getFilename());

      if (debug) {
	System.out.println("DrawApp.initProjectManager(): itemName=" + 
			   itemName + " filename=" + doc.getFilename());
      }

      Project project = projectManager.getDefaultProject();
      project.setDefaultDir(frame.getDefaultDir());
      project.setWorkingDir(frame.getDefaultDir());
      if (pinfo.projType == ProjectType.UML) {
	ProjectFolder mfolder = new ProjectFolder("Model",
						  ProjectFolder.Type.UML_Model);
	projectManager.addProjectItem(mfolder, project, false);
	projectManager.addProjectItem(item, mfolder, false);
      } else {
	projectManager.addProjectItem(item, project, false);
      }
      projectManager.setCurrentItem(item);
      initProjectModel(project);

      if (projectListener != null) {
	projectListener.refresh();
      }      
    }
  }

  protected void setProjectListener(ProjectPane projectPane) {
    if (projectManager == null) {
      projectMode = true;
      projectManager = new ProjectManager(this);
      ProjectItem item = new ProjectItem(doc);
      Project project = projectManager.getDefaultProject();
      projectManager.addProjectItem(item, project, false);
      initProjectModel(project);
      if (projectPane != null) {
	projectPane.setProjectManager(projectManager);
      }
    }
    projectManager.setProjectListener(projectPane);
  }

  public ProjectListener getProjectListener() {
    return projectListener;
  }

  protected void loadInitialProjects() {
    List<String> initProjects = userProfile.getInitProjects();
    String curproj = userProfile.getCurrentProject(); 

    //System.out.println("initProjects: " + initProjects); 
    logInfo("initProjects: " + initProjects); 

    int n = 0; 
    if (initProjects != null && 
	(n = initProjects.size()) > 0) {
      Collections.sort(initProjects);

      Project proj = projectManager.getDefaultProject();
      Iterator<String> iter = initProjects.iterator();
      int i = 0; 
      while (iter.hasNext()) {     
	String projname = iter.next();
	try {
	  loadProject(projname, true);
	  setSplashScreenProgress(75 + 20 * (i++) / n);      
	} catch (IOException e) {
	  // missing project, ignore
	  logInfo("Missing project: " + projname + ", Ignored.\n\t" + e.getMessage());
	} catch (ProjectException e) {
	  // project with error, ignore
	  logError("Error reading project: " + projname + ", Ignored.\n\t" + e.getMessage());
	} catch (DocumentException e) {
	  // 
	  logError("Error reading project: " + projname + ", Ignored.\n\t" + e.getMessage());
	}
      }

      setSplashScreenProgress(96);

      if (proj != null && 
	  projectManager.containsProject(proj)) {
	if (projectManager.getNumOfProjects() > 1) {
	  // remove the default project, if initProjects are not empty 
	  projectManager.removeProject(proj);

	  if (curproj != null) { 
	    Project p = projectManager.findProject(curproj); 
	    if (p != null) { 
	      projectManager.setCurrentProject(p);
	      if (projectListener != null) {
		projectListener.projectSelected(p);
	      }      
	    }
	  }
	} else {
	  ProjectItem item = projectManager.getCurrentItem();
	  if (item != null) {
	    String filename = proj.getWorkingDir() + File.separator+ item.getDocumentPath();
	    try {
	      doc.openFile(filename, XML_FILE_TYPE);
	      // item.loadDocument();
	    } catch (IOException e) {
	      // recover
	      // System.err.println("initProjectManager: " + e.getMessage());
	      logError("Error loading " + filename + "\n\t" + e.getMessage());
	      ProjectFolder parent = item.getParent();
	      if (parent != null) { 
		parent.removeItem(item);
	      }
	    } catch (DocumentException e) {
	      // recover
	      // System.err.println("initProjectManager: " + e.getMessage());
	      logError("Error loading " + filename + "\n\t" + e.getMessage());
	      ProjectFolder parent = item.getParent();
	      if (parent != null) { 
		parent.removeItem(item);
	      }
	    }
	  }
	}
      }
    }

    //setSplashScreenProgress(98);
    if (projectPane != null) {
      BreadcrumbAdapter bcb = new BreadcrumbAdapter(projectPane);
      setCanvasTitleDecoration(bcb); 
      bcb.updatePath();
    }
  }

  protected void createScratchPad() { 
    initDocView();
    doc.setName("*Scratch*");
    doc.setDocumentType(Document.DocumentType.Generic_Diagram);
    addViewTab(view);
  }

  protected JMenu createTestMenu() {
    return null;
  }

  public void setCanvasDimension(float width, float height) {
    view.setVirtualCanvasRectangle(new Rectangle2DFloat(0, 0, width, height));
    doc.setCanvasWidth(width);
    doc.setCanvasHeight(height);
  }

  public static void fitCanvas(SimpleDrawView view) { 
    fitCanvas(view, 0, 0);
  }

  public static void fitCanvas(SimpleDrawView view, float minWidth, float minHeight) { 
    if (view != null) {
      SimpleDrawDocument doc = view.getSimpleDrawDocument();
      float margin = 20; 
      Rectangle2D rect = doc.getBounds();
      float dx = margin - (float) rect.getX();
      float dy = margin - (float) rect.getY();
      doc.moveAll(dx, dy); 
      float width = (float) rect.getWidth() + 2 * margin;
      float height = (float) rect.getHeight() + 2 * margin;
      if (width < minWidth) { 
	width = minWidth;
      }
      if (height < minHeight) {
	height = minHeight;
      }
      doc.setCanvasWidth(width);
      doc.setCanvasHeight(height);
      view.setVirtualCanvasRectangle(new Rectangle2DFloat(0, 0, width, height));
    }
  }

  // adjust the canvas size to fit the current document 
  public void fitCanvas() { 
    Rectangle2D dim = doc.getBounds();
    float dw = (float) (dim.getX() + dim.getWidth()) + view.getMarginWidth();
    float dh = (float) (dim.getY() + dim.getHeight()) + view.getMarginHeight();
    float cw = doc.getCanvasWidth();
    float ch = doc.getCanvasHeight();
    boolean needAdjust = false; 
    if (cw < dw) {
      doc.setCanvasWidth(dw);
      needAdjust = true;
      cw = dw;
    }
    if (ch < dh) {
      doc.setCanvasHeight(dh);
      needAdjust = true;
      ch = dh;
    }
    if (needAdjust) { 
      setCanvasDimension(cw, ch);
    }
  }

  protected boolean saveCurrentFileDialog(String msgPrefix) {
    return saveFileDialog(doc, msgPrefix);
  }

  protected boolean saveFileDialog(SimpleDrawDocument doc, String msgPrefix) {
    if (doc == null) {
      return true;
    }
    
    boolean confirm = false;
    if (doc.isDocumentChanged()) {
      int result = JOptionPane.showConfirmDialog(frame.getJFrame(),
						 getResourceString(msgPrefix + ".message"), 
						 getResourceString(msgPrefix + ".title"),
						 JOptionPane.YES_NO_CANCEL_OPTION);
      if (result == JOptionPane.YES_OPTION) {
	try {
	  if (currentFilename != null && 
	      !currentFilename.startsWith("Untitled")) {
	    saveFile();
	  } else {
	    saveAsFile();
	  }
	} catch (IOException e) {
	  String msg1 = "Save file error: " + currentFilename;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save File Error", JOptionPane.ERROR_MESSAGE);
	} catch (DocumentException e) {
	  String msg1 = "Save file error: " + currentFilename;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save File Error", JOptionPane.ERROR_MESSAGE);
	}

	confirm = true;
      } else if (result == JOptionPane.NO_OPTION) {
	confirm = true;
      }

      if (debug) {
	System.out.println("DrawApp.saveCurrentFileDialog() result=" + result);
      }
    } else {
      confirm = true;
    }
    return confirm;
  }

  protected boolean saveProjectItemsDialog(String msgPrefix) {
    boolean confirm = false;
    if (projectManager != null && 
	projectManager.hasChangedProjects()) {
      String message = getResourceString(msgPrefix + ".message");
      String title = getResourceString(msgPrefix + ".title");      
      int result = 
	JOptionPane.showConfirmDialog(frame.getJFrame(), message, title,
				      JOptionPane.YES_NO_CANCEL_OPTION);
      if (result == JOptionPane.YES_OPTION) {
	try {
	  projectManager.saveAllProjects();
	} catch (IOException e) {
	  String msg1 = "Save project error";
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", 
					JOptionPane.ERROR_MESSAGE);
	} catch (DocumentException e) {
	  String msg1 = "Save project error";
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", 
					JOptionPane.ERROR_MESSAGE);
	}
	confirm = true;
      } else if (result == JOptionPane.NO_OPTION) {
	// remove new document never saved 
	projectManager.removeUnsavedNewProjects();
	confirm = true;
      }
    } else {
      confirm = true;
    }
    return confirm;
  }

  public boolean deleteProjectItemDialog(String msgPrefix, String itemname) {
    boolean confirm = false;
    String title = getResourceString(msgPrefix + ".title");
    String message = getResourceString(msgPrefix + ".message");
    message = MessageFormat.format(message, itemname);
    int result = JOptionPane.showConfirmDialog(frame.getJFrame(), message, title, 
					       JOptionPane.YES_NO_OPTION);
    if (result == JOptionPane.YES_OPTION) {
      confirm = true;
    }
    return confirm;
  }

  public boolean exitApp() {
    boolean doExit = true;
    if (projectManager != null) {
      doExit = saveProjectItemsDialog("ExitProjectDialog");
    } else {
      doExit = saveCurrentFileDialog("ExitDialog");
    }
    //System.out.println("DrawApp.exitFrame() " + doExit);

    if (doExit) {
      userProfile.saveUserConfig();
      return true;
    }
    return false; 
  }

  //
  // Callback for switching tabbed pane
  //
  protected void tabbedViewChanged(SimpleDrawView view) {
    if (view != null) {
      if (false) System.out.println("DrawApp.tabbedViewChanged()");

      SimpleDrawDocument doc = view.getSimpleDrawDocument();
      setDocView(doc, view);
      view.setDrawToolkit(toolkit);
      view.activateView();
      if (docChangeListener != null) {
	docChangeListener.reset();
      }
      currentFilename = doc.getFilename();
      currentFiletype = doc.getFiletype();

      int sf = (int) (view.getScaleFactor() * 100);
      zoomControl.setZoomDisplay(sf);

      if (templatePanel != null) {
	Document.DocumentType type = doc.getDocumentType();
	String dname = null;
	if (Document.DocumentType.Generic_Diagram == type) {
	  dname = "Generic Diagram";
	} else if (Document.DocumentType.Flow_Graph_Diagram == type) {
	  dname = "Flow Graph";
	} else if (Document.DocumentType.Visual_Class_Diagram == type) {
	  dname = "Visual Class";	
	} else if (Document.DocumentType.UML_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Class_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Activity_Diagram == type) {
	  dname = "UML - Activity";
	} else if (Document.DocumentType.UML_Communication_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Component_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Composite_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Deployment_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Interaction_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Object_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Package_Diagram == type) {
	  dname = "UML - Package";
	} else if (Document.DocumentType.UML_Sequence_Diagram == type) {
	  dname = "UML - Sequence";
	} else if (Document.DocumentType.UML_State_Diagram == type) {
	  dname = "UML - State";
	} else if (Document.DocumentType.UML_Timimg_Diagram == type) {
	  dname = "UML - Class";
	} else if (Document.DocumentType.UML_Use_Case_Diagram == type) {
	  dname = "UML - Use Case";
	}
	if (dname != null) {
	  templatePanel.selectTemplate(dname);
	}
      }

      runnerManager.startCheckerRunner(doc);

      if (projectMode && 
	  projectListener != null) {
	projectListener.documentSelected(doc);
      }
      setFrameTitle();
    }
  }

  public void updateDocumentTitle(SimpleDrawDocument doc) {
    if (doc != null) {
      setCurrentFilename(doc.getFilename());
      updateTabTitle(doc);

      // docChangeListener.reset();
    }
  }

  public void newStencil() { 
    Object act = actions.get("NewStencil");
    if (act instanceof NewStencilListener) {
      NewStencilListener ni = (NewStencilListener) act;
      ni.actionPerformed(null);
    }
  }

  public void newStencil(String name,
			 String filename,
			 Document.DocumentType type) {
    if (filename != null) { 
      if (name == null) { 
	name = SimpleDrawDocument.getNameFromFilename(filename);
      }
      setCurrentFilename(filename);

      initDocView();
      doc.setFilename(filename);
      doc.setName(name);
      doc.setNamespace(frame.getNamespaceURI(), frame.getNamespacePrefix());
      doc.setDocumentType(type);
      doc.setFiletype(XML_STENCIL_FILE_TYPE);
      view.setStencilMode(true);

      if (doc instanceof GraphDocument) {
	GraphDocument gdoc = (GraphDocument) doc;
	gdoc.initModel();
      }
      
      addViewTab(view, getIcon("Stencil"));
    }

  }

  // show diglog, input name and properties 
  public void newProjectItem(ProjectFolder parent) {
    // newProjectItem(parent, Document.DocumentType.Default_Diagram);
    Object act = actions.get("NewProjectItem");
    if (act instanceof NewProjectItemListener) {
      NewProjectItemListener ni = (NewProjectItemListener) act;
      ni.setParent(parent);
      ni.actionPerformed(null);
    }
  }

  // no dialog, use default value 
  public void newProjectItem(ProjectFolder parent, Document.DocumentType type) {
    newProjectItem(parent, null, type, -1);
  }

  // no dialog, use default value 
  public void newProjectItem(ProjectFolder parent, 
			     String filename,
			     Document.DocumentType type) {
    newProjectItem(parent, filename, type, -1);
  }

  // no dialog, use default value 
  public void newProjectItem(ProjectFolder parent, 
			     String filename,
			     Document.DocumentType type,
			     int pos) {
    if (debug) {
      System.out.println("DrawApp.newProjectItem(): filename=" + filename + " type=" + type);
    }

    if (parent == null) {
      ProjectItem cur = projectManager.getCurrentItem();
      if (cur != null) {
	if (cur instanceof ProjectFolder) { 
	  parent = (ProjectFolder) cur;
	} else { 
	  parent = cur.getParent();
	}
      }
      if (parent == null) {
	parent = projectManager.getCurrentProject();
      }
    }

    String name;
    String ext = "." + getDocumentExtension(type);
    if (filename == null || filename.length() == 0) {
      String newname = Document.getDefaultFilename(type);
      name = parent.getNewItemName(newname);
      filename = name + ext;
    } else {
      if (filename.endsWith(ext)) {
	name = SimpleDrawDocument.getNameFromFilename(filename);
      } else { 
	name = filename; 
	filename = filename + ext;
      }
    }
    setCurrentFilename(filename);

    initDocView();
    doc.setFilename(filename);
    doc.setName(name);
    doc.setNamespace(frame.getNamespaceURI(), frame.getNamespacePrefix());
    doc.setDocumentType(type);

    if (doc instanceof GraphDocument) {
      GraphDocument gdoc = (GraphDocument) doc;
      gdoc.initModel();
      // System.out.println("DrawApp.newProjectItem(): model != null: " +
      // (model != null));
    }

    ProjectItem item = new ProjectItem(doc);
    projectManager.addProjectItem(item, parent, pos);
    projectManager.setCurrentItem(item);
    addViewTab(view, getDocumentIcon(type));
  }

  protected Icon getDocumentIcon(Document document) {
    if (document != null) { 
      return getDocumentIcon(document.getDocumentType());
    }
    return null;
  }

  public Icon getDocumentIcon(Document.DocumentType type) { 
    if (type != null) { 
      switch (type) { 
      case UML_Class_Diagram:
	return icons.get("UMLClassDiagram.Class1"); 
      case UML_Activity_Diagram:
	return icons.get("UMLActivityDiagram.SendEvent");
      case UML_Package_Diagram:
	return icons.get("UMLPackageDiagram.Package1");
      case UML_Sequence_Diagram:
	return icons.get("UMLSequenceDiagram.Object");
      case UML_State_Diagram:
	return icons.get("UMLStateDiagram.StateMachine");
      case UML_Use_Case_Diagram:
	return icons.get("UMLUseCaseDiagram.UseCase");
      }
    }
    return null;
  }

  public void addDocument(SimpleDrawDocument doc) { 
    initDocView(doc); 
    addViewTab(view, getDocumentIcon(doc.getDocumentType()));
  }

  public String getDocumentFilename(String name, Document.DocumentType type) {
    if (name != null) {
      String ext = "." + getDocumentExtension(type);
      if (!name.endsWith(ext)) {
	return name + ext;
      } else {
	return name;
      }
    }
    return null;
  }

  // AutoSaveListener 
  public void autoSaveAllDocuments() { 
    if (tabbedPane != null) {
      int n = tabbedPane.getTabCount();
      for (int i = 0; i < n; i++) {
	java.awt.Component comp = tabbedPane.getComponentAt(i);
	if (comp instanceof SimpleDrawView) {
	  SimpleDrawView view = (SimpleDrawView) comp;
	  SimpleDrawDocument doc = view.getSimpleDrawDocument();
	  doc.autoSaveFile();
	}
      }
    }
  }

  protected void newFile() {
    if (projectMode) {
      newProjectItem(null);
    } else {
      String name = "Untitled" + (untitledCount++);
      String filename = name + "." + getDocumentExtension(null);
      setCurrentFilename(filename);
      doc.newFile();
      setChanged(false);
      doc.setFilename(filename);
      doc.setName(name);
      doc.setNamespace(frame.getNamespaceURI(), frame.getNamespacePrefix());
    }
    view.refresh();
  }

  protected void newTab() {
    /*
     * doc = new DrawAppDocument(this); view = new DrawAppView(this, doc);
     * view.setShowRuler(showRuler);
     */
    initDocView();
    String name = "Untitled" + (untitledCount++);
    String filename = name + "." + getDocumentExtension(null);
    setCurrentFilename(filename);
    doc.setFilename(filename);
    doc.setName(name);
    doc.setNamespace(frame.getNamespaceURI(), frame.getNamespacePrefix());
    addViewTab(view, getDocumentIcon(doc.getDocumentType()));
  }

  public void loadFile(String filename, String docname) 
    throws IOException, DocumentException {
    //System.out.println("DrawApp.loadFile() " + filename); 

    openFile(filename, docname, XML_FILE_TYPE, Document.DocumentType.Generic_Diagram,
	     null, false);
  }

  public void loadFile(String filename, String docname, int filetype) 
    throws IOException, DocumentException {
    System.out.println("DrawApp.loadFile() " + filename); 

    openFile(filename, docname, filetype, Document.DocumentType.Generic_Diagram,
	     null, false);
  }

  public void loadFile(String filename, String docname, Document.DocumentType doctype) 
    throws IOException, DocumentException {
    openFile(filename, docname, XML_FILE_TYPE, doctype,
	     null, false);
  }

  protected void openFile(String filename, int filetype) 
    throws IOException, DocumentException {
    openFile(filename, null, filetype, Document.DocumentType.Generic_Diagram,
	     null, projectMode);
  }

  protected void openFile(String filename, int filetype, ProjectFolder parent) 
    throws IOException, DocumentException {
    openFile(filename, null, filetype, Document.DocumentType.Generic_Diagram,
	     parent, projectMode);
  }

  protected void openFile(String filename, 
			  int filetype, 
			  ProjectFolder parent,
			  boolean projectMode) 
    throws IOException, DocumentException {
    openFile(filename, null, filetype, Document.DocumentType.Generic_Diagram,
	     parent, projectMode);
  }

  protected void openFile(String filename, String docname, 
			  int filetype, Document.DocumentType doctype, 
			  ProjectFolder parent, boolean projectMode) 
    throws IOException, DocumentException {
    setCurrentFilename(filename);
    String name;
    if (docname != null) {
      name = docname;
    } else {
      name = SimpleDrawDocument.getNameFromFilename(filename);
    }
    currentFiletype = filetype;

    /*
     * doc = new DrawAppDocument(this); view = new DrawAppView(this, doc);
     * view.setShowRuler(showRuler);
     */    

    if (projectMode && 
	projectManager != null) {
      Project project = projectManager.getCurrentProject();
      if (!project.hasItem(name)) {
	initDocView();

	doc.setDocumentType(doctype);
	doc.openFile(filename, filetype);
	doc.setNamespace(frame.getNamespaceURI(), frame.getNamespacePrefix());
	doc.setFilename(filename);
	doc.setFiletype(filetype);
	doc.setName(name);

	if (parent == null) {
	  ProjectItem cur = projectManager.getCurrentItem();
	  if (cur != null) {
	    parent = cur.getParent();
	  }
	  if (parent == null) {
	    parent = projectManager.getCurrentProject();
	    ProjectItem item = parent.findItem("Model");
	    if (item != null && item instanceof ProjectFolder) {
	      parent = (ProjectFolder) item;
	    }
	  }
	}
	ProjectItem item = new ProjectItem(doc);
	projectManager.addProjectItem(item, parent);
	
	//System.out.println("DrawApp.openFile() @1 doctype=" + doctype);

	addViewTab(view, getDocumentIcon(doc.getDocumentType()));
	addFileEntry(filename, filetype);
	displayMessage(getResourceString("FileOpened.message") + 
		       " " + filename);
      } else {
	displayMessage(getResourceString("FileAlreadyOpened.message") + 
		       " " + filename);
      }
    } else {
      // load file without adding to the project manager
      initDocView();

      if (projectManager != null) { 
	projectManager.setCurrentItem(null);
	//projectListener.refresh();
	projectListener.projectItemSelected(null);
      }

      doc.setDocumentType(doctype);
      doc.openFile(filename, filetype);
      doc.setNamespace(frame.getNamespaceURI(), frame.getNamespacePrefix());
      doc.setFilename(filename);
      doc.setFiletype(filetype);
      doc.setName(name);

      //System.out.println("DrawApp.openFile() @2 doctype=" + doctype);

      Icon icon = null; 
      if (filetype == DocumentConstants.XML_STENCIL_FILE_TYPE) {
	view.setStencilMode(true);
	icon = getIcon("Stencil");
      } else { 
	icon = getDocumentIcon(doctype);
      }

      addViewTab(view, icon);
      addFileEntry(filename, filetype);

      displayMessage(getResourceString("FileOpened.message") + " " + filename);
    }
    fitCanvas(view, doc.getCanvasWidth(), doc.getCanvasHeight());
    view.refresh();
  }

  protected void addFileEntry(String filename, int filetype) {
    FileEntry entry = new FileEntry(filename, filetype);
    String shortname = SimpleDrawDocument.getNameFromFilename(filename);
    recentlyOpenedFiles.put(shortname, entry);

    if (recentlyOpenedFilesMenu != null) {
      int n = recentlyOpenedFilesMenu.getItemCount();
      JMenuItem menuitem = null;
      for (int i = 0; i < n; i++) {
	JMenuItem mi = recentlyOpenedFilesMenu.getItem(i);
	String text = mi.getText();
	String cmd = mi.getActionCommand();
	if (shortname.equals(text)) {
	  recentlyOpenedFilesMenu.remove(i);
	  menuitem = mi;
	  break;
	} else if ("NoFile".equals(cmd)) {
	  recentlyOpenedFilesMenu.remove(i);
	}
      }
      if (menuitem == null) {
	menuitem = new JMenuItem();
	menuitem.setText(shortname);
	menuitem.setActionCommand(shortname);
	if (recentlyOpenedFilesAction != null) {
	  menuitem.addActionListener(recentlyOpenedFilesAction);
	}
      }
      recentlyOpenedFilesMenu.insert(menuitem, 0);
    }

  }

  protected void loadProject(String path) 
    throws IOException, ProjectException, DocumentException {
    loadProject(path, false);
  }

  protected void loadProject(String path, boolean override) 
    throws IOException, ProjectException, DocumentException {
    if (path != null) {
      Project project = new Project(this);
      initProjectModel(project);
      project.setDefaultDir(frame.getDefaultDir());

      File f = new File(path);
      String workingDir = null;
      if (f.isAbsolute()) {
	workingDir = f.getParent();
      }
      if (workingDir == null) {
	workingDir = frame.getDefaultDir();
      }
      project.setWorkingDir(workingDir);

      project.loadProjectFromPath(path);
      String name = project.getName();
      if (!projectManager.hasProject(name) || override) {
	if (override) {
	  Project p0 = projectManager.findProject(name);
	  projectManager.removeProject(p0);
	}
	projectManager.addProject(project);
	displayMessage(getResourceString("ProjectOpened.message") + " " + name);
      } else {
	displayMessage(getResourceString("ProjectAlreadyOpened.message") + " " + name);
      }

      if (projectManager != null) {
	ProjectItem item = projectManager.findItem(doc);
	if (item != null) {
	  projectManager.setCurrentItem(item);
	}
      }
    }
  }

  public void saveProject() {
    if (projectManager != null) {
      Project project = projectManager.getCurrentProject();
      if (project != null) {
	String name = project.getName();
	try {
	  projectManager.saveCurrentProject();
	  displayMessage(getResourceString("ProjectSaved.message") + 
			 " " + name);
	} catch (IOException e) {
	  String msg1 = "Save project error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", 
					JOptionPane.ERROR_MESSAGE);
	} catch (DocumentException e) {
	  String msg1 = "Save project error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", 
					JOptionPane.ERROR_MESSAGE);

	}
      }
    }
  }

  protected void saveProjectAs(String filename) {
    if (projectManager != null) {
      Project project = projectManager.getCurrentProject();
      if (project != null) {
	String name = project.getName();
	try {
	  project.setProjectPath(filename);
	  project.saveProject();

	  displayMessage(getResourceString("ProjectSaved.message") + 
			 " " + name);
	} catch (IOException e) {
	  String msg1 = "Save project error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", JOptionPane.ERROR_MESSAGE);
	} catch (DocumentException e) {
	  String msg1 = "Save project error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", JOptionPane.ERROR_MESSAGE);
	}
      }
    }
  }

  protected void loadProjectArchive(String path) 
    throws IOException, ProjectException, DocumentException {
    loadProjectArchive(path, false);
  }

  protected void loadProjectArchive(String path, boolean override)
      throws IOException, ProjectException, DocumentException {
    if (path != null) {
      Project project = new Project(this);
      initProjectModel(project);
      project.setDefaultDir(frame.getDefaultDir());

      File f = new File(path);
      String workingDir = null;
      if (f.isAbsolute()) {
	workingDir = f.getParent();
      }
      if (workingDir == null) {
	workingDir = frame.getDefaultDir();
      }
      project.setWorkingDir(workingDir);

      project.loadProjectArchive(path);
      String name = project.getName();
      if (!projectManager.hasProject(name) || override) {
	if (override) {
	  Project p0 = projectManager.findProject(name);
	  projectManager.removeProject(p0);
	}
	projectManager.addProject(project);
	displayMessage(getResourceString("ProjectOpened.message") + " " + name);
      } else {
	displayMessage(getResourceString("ProjectAlreadyOpened.message") + " " + name);
      }

      if (projectManager != null) {
	ProjectItem item = projectManager.findItem(doc);
	if (item != null) {
	  projectManager.setCurrentItem(item);
	}
      }
    }
  }

  protected void saveProjectArchive(String filename) {
    if (projectManager != null) {
      Project project = projectManager.getCurrentProject();
      if (project != null) {
	String name = project.getName();
	try {
	  project.saveProjectArchive(filename);
	  displayMessage(getResourceString("ProjectArchiveSaved.message") + " " + name);
	} catch (IOException e) {
	  String msg1 = "Save project archive error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Archive Error", 
					JOptionPane.ERROR_MESSAGE);
	} catch (DocumentException e) {
	  String msg1 = "Save project archive error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Archive Error", 
					JOptionPane.ERROR_MESSAGE);
	}
      }
    }
  }

  public void closeTab(SimpleDrawDocument doc) { 
    if (doc != null) { 
      int i = findViewTab(doc);
      if (i >= 0) { 
	SimpleDrawView view = getViewTabAt(i);
	if (closeTabView(view)) {
	  removeView(view);
	}
      }
    }
  }

  // true: to close, false: not close 
  protected boolean closeTabView(SimpleDrawView view) { 
    if (view != null) {  
      if (tabbedPane != null &&	
	  tabbedPane.getTabCount() <= 1) {
	return false;
      } 

      SimpleDrawDocument doc = view.getSimpleDrawDocument();
      if (doc.isDocumentChanged()) { 
	if (!saveFileDialog(doc, "CloseTabDialog")) {
	  return false;
	}
      }

      if (projectManager != null) { 
	ProjectItem item = projectManager.findItem(doc);
	if (item != null) { 
	  item.setDocument(null);
	}
      }

      if (view.isStencilMode()) { 
	stencilPanel.closeStencilEditing(doc.getName());
      }
    }
    return true;
  }

  protected void closeCurrentProject() {
    if (projectManager != null) {
      Project project = projectManager.getCurrentProject();
      if (project != null) { 
	String name = project.getName();
	try {
	  projectManager.saveProject(project);
	  projectManager.removeProject(project);
	} catch (IOException e) {
	  String msg1 = "Save project error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", 
					JOptionPane.ERROR_MESSAGE);
	} catch (DocumentException e) {
	  String msg1 = "Save project error: " + name;
	  String msg2 = e.getMessage();
	  displayMessage(msg1);
	  JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + msg2,
					"Save Project Error", 
					JOptionPane.ERROR_MESSAGE);
	}
      }

      notifyTabClosed();
    }
  }

  public void notifyTabClosed() { 
    if (tabbedPane != null) {
      int n = tabbedPane.getTabCount();
      if (n == 0) { 
	createScratchPad();
      }
    }
  }
 
  protected void saveFile() 
    throws IOException, DocumentException {
    if (debug) {
      System.out.println("DrawApp.saveFile()");
    }
    
    if (projectManager != null &&
	!view.isStencilMode()) {
      ProjectItem item = projectManager.getCurrentItem();
      if (item != null) { 
	item.saveDocument();
      } else { 
	saveFile(doc, false);
      }
    } else { 
      saveFile(doc, view.isStencilMode());
    }
  }

  public void saveFile(SimpleDrawDocument doc) 
    throws IOException, DocumentException {
    saveFile(doc, false);
  }

  public void saveFile(SimpleDrawDocument doc, boolean isStencil) 
    throws IOException, DocumentException {
    if (debug) { 
      System.out.println("DrawApp.saveFile() " + doc.getName() + " " + isStencil);
    }

    String filename = doc.getFilename();
    int filetype = doc.getFiletype();
    if (filename == null) {
      filename = currentFilename;
      if (currentFilename == null) {
	filename = "Untitled" + (untitledCount++);
	setCurrentFilename(filename);
      }

      doc.setFiletype(currentFiletype);
      doc.setFilename(filename);
      doc.setName(SimpleDrawDocument.getNameFromFilename(filename));
    }

    if (debug) { 
      System.out.println("DrawApp.saveFile() " + filename);
    }
    String loc = null;
    if (projectMode && projectManager != null) {
      Project proj = projectManager.getCurrentProject();
      if (proj != null) {
	loc = proj.getWorkingDir();
      }
    }
    File f = new File(filename);
    if (!f.isAbsolute() && 
	loc != null) {
      filename = loc + File.separator + filename;
    }

    f = new File(filename);
    File dir = f.getParentFile();
    Project.createDir(dir);

    doc.saveFile(filename, filetype, false);
    filename = doc.getFilename();
    addFileEntry(filename, filetype);
    doc.setName(SimpleDrawDocument.getNameFromFilename(filename));

    filename = Project.getRelativePath(loc, filename);
    doc.setFilename(filename);

    if (debug) {
      System.out.println("DrawApp.saveFile(): filename=" + filename);
      // System.out.println("DrawApp.saveFile(): filename=" + filename + "
      // ext=" + ext);
    }

    if (isStencil && 
	stencilPanel != null) { 
      stencilPanel.loadStencil(doc.getName()); 
    }

    displayMessage(getResourceString("FileSaved.message") + " "	+ filename);
  }

  protected void saveFileAs(String filename, int filetype) 
    throws IOException, DocumentException {
    // System.out.println("DrawApp.saveFileAs()");

    currentFilename = filename;
    doc.setFiletype(filetype);
    doc.saveFile(filename, filetype, true);
    filename = doc.getFilename();

    doc.setName(SimpleDrawDocument.getNameFromFilename(filename));
    addFileEntry(filename, filetype);

    updateTabTitle(doc);
    String loc = null;
    if (projectMode && projectManager != null) {
      Project proj = projectManager.getCurrentProject();
      if (proj != null) {
	loc = proj.getWorkingDir();
      }
      ProjectItem item = projectManager.findItem(doc);
      if (item != null) {
	item.updateName();
	if (projectListener != null) {
	  projectListener.projectItemRenamed(item);
	}
      }
    }

    filename = Project.getRelativePath(loc, filename);
    doc.setFilename(filename);
    displayMessage(getResourceString("FileSaved.message") + " "	+ filename);
  }

  protected String normalizeFilename(String filename) {
    return normalizeFilename(filename, getExtension(currentFiletype));
  }

  protected String normalizeFilename(String filename, String ext) {
    if (filename != null) {
      String fext = null;
      int i = filename.lastIndexOf('.');
      if (i > 0 && i < filename.length() - 1) {
	fext = filename.substring(i + 1).toLowerCase();
      }
      if (ext != null && fext == null) {
	filename += ("." + ext);
      }
      return filename;
    }
    return null;
  }

  protected void saveAsFile() {
    Object act = actions.get("SaveAsFile");
    if (act instanceof SaveAsFileListener) {
      SaveAsFileListener sa = (SaveAsFileListener) act;
      sa.saveAs();
    }
  }

  protected void doPageSetup() {
    view.doPageSetup();
  }

  protected void doPrintPreview() {
    view.doPrintPreview();
  }

  protected void doPrint() {
    view.doPrint();
  }

  protected void undoCommand() {
    doc.undoCommand();
  }

  protected void redoCommand() {
    doc.redoCommand();
  }

  protected void cut() {
    editManager.cut();
  }

  protected void copy() {
    editManager.copy();
  }

  protected void paste() {
    editManager.paste();
  }

  public void setChanged(boolean changed) {
    changeLabel.setIcon(changed ? modifiedIcon : unmodifiedIcon);
    changeLabel.setToolTipText(changed ? modifiedString : unmodifiedString);
    // changeLabel.setText(changed ? modifiedString : unmodifiedString);
    // changeLabel.setForeground(changed ? modifiedColor : unmodifiedColor);

    if (tabbedPane != null) { 
      LookAndFeelManager lnfManager = LookAndFeelManager.getInstance();
      if (lnfManager.isSubstanceLnF()) { 
	Component comp = tabbedPane.getSelectedComponent();
	if (comp instanceof JComponent) {
	  JComponent jc = (JComponent) comp; 
	  jc.putClientProperty(org.jvnet.substance.SubstanceLookAndFeel.WINDOW_MODIFIED,
			       changed ? Boolean.TRUE : Boolean.FALSE);	
	}
      }
    }
  }

  /*
   * ZOOM Panel
   */

  protected JLabel changeLabel;

  protected String modifiedString = "Modified";

  protected String unmodifiedString = "Unmodified";

  protected Color modifiedColor = Color.yellow;

  protected Color unmodifiedColor = Color.blue;

  protected Icon modifiedIcon;

  protected Icon unmodifiedIcon;

  protected JComponent initPanel(String name) {
    //System.out.println("DrawApp.initPanel() name=" + name);
    	
    JComponent comp = null;
    if ("Tree".equals(name)) {
      DocumentTreePane docTree = new DocumentTreePane(this, icons);
      comp = docTree;
      docChangeListener = docTree;
    } else if ("GraphTree".equals(name)) {
      if (this instanceof GraphApp) {
	GraphDocumentTreePane graphTree = new GraphDocumentTreePane((GraphApp) this, icons);
	comp = graphTree;
	docChangeListener = graphTree;
      }
    } else if ("ProjectTree".equals(name)) {
      //System.out.println("DrawApp.initPanel() name=" + name + " @1 " + (this instanceof GraphApp) + " " + (isProjectMode()));
      if (this instanceof GraphApp && 
	  isProjectMode()) {
	//System.out.println("DrawApp.initPanel() name=" + name + " @2");	
	tabbedProjectPanel = new TabbedProjectPanel((GraphApp) this, icons);
	projectPane = tabbedProjectPanel.getProjectPane();
	comp = tabbedProjectPanel;
	docChangeListener = tabbedProjectPanel;
	projectListener = projectPane;
	setProjectListener(projectPane);
      }
    } else if ("TabbedTree".equals(name)) {
      if (this instanceof GraphApp) {
	TabbedDocumentTreePanel tabbedTree = 
	  new TabbedDocumentTreePanel((GraphApp) this, icons);
	comp = tabbedTree;
	docChangeListener = tabbedTree;
      }
    } else if ("Attributes".equals(name)) {
      if (this instanceof GraphApp) {
	// comp = propPane;
	propPane = new DrawShapePropertyPane(this, null, false);
	modelPropPane = new ModelPropertiesPane(this);
	tabbedPropPane = new TabbedAttributePane(modelPropPane, propPane);
	comp = tabbedPropPane;
      } else {
	propPane = new DrawShapePropertyPane(this);
	comp = propPane;
      }
    } else if ("Templates".equals(name)) {
      comp = makeTemplatePanel();
    } else if ("Stencils".equals(name)) {
      comp = makeStencilPanel();
    } else if ("Output".equals(name)) {
      comp = new OutputPane();
    }
    return comp;
  }

  protected void makeViewOptionPanel() {
    zoomControl = new ZoomControl(this);
    JComponent[] zoomComp = zoomControl.makeComponents(useZoomSlider);
    int n = zoomComp.length + 3; 
    if (showViewOptions) {
      n += 2; 
    }
    JComponent[] zoombar = new JComponent[n];
    int i;
    for (i = 0; i < zoomComp.length; i++) { 
      zoombar[i] = zoomComp[i];
    }

    if (showViewOptions) {
      Icon viewIcon = icons.get("ViewOptionIcon");
      JButton viewButton = new JButton(viewIcon);
      viewButton.setToolTipText(getResourceString("ViewOptions.text"));
      viewButton.addActionListener(new ViewOptionListener());
      zoombar[i++] = viewButton;
      zoombar[i++] = new JSeparator(SwingConstants.VERTICAL);
    }

    changeLabel = new JLabel();
    modifiedIcon = icons.get("editedIcon");
    unmodifiedIcon = icons.get("uneditedIcon");
    modifiedString = getResourceString("Modified.text");
    unmodifiedString = getResourceString("Unmodified.text");
    setChanged(false);
    zoombar[i++] = changeLabel;

    Icon infoIcon = icons.get("jdeInfoIcon");
    Icon thumbIcon = icons.get("jdePhotoIcon");
    JLabel infoLabel = new JLabel(infoIcon); 
    JLabel thumbLabel = new JLabel(thumbIcon); 
    infoLabel.addMouseListener(new DocInfoListener());
    thumbLabel.addMouseListener(new ThumbnailListener());
    zoombar[i++] = infoLabel;
    zoombar[i++] = thumbLabel;

    statusBarLeft = zoombar;
  }

  protected void makeLocationLabel() {
    locationLabel = new JLabel("   (0, 0)   ");
    statusBarRight = locationLabel;
  }

  protected JComponent makeTemplatePanel() {
    TemplatePanel temp = new TemplatePanel();
    Iterator<ToolPanel> iter = templatePanels.iterator();
    while (iter.hasNext()) {
      ToolPanel toolPanel = iter.next();
      temp.addTemplate(toolPanel);
    }
    templatePanel = temp;
    return temp;
  }

  protected JComponent makeStencilPanel() {
    stencilPanel = new StencilPanel(this);
    if (stencils.isEmpty()) { 
      stencilPanel.initStencil();
    } else {
      Iterator<String> iter = stencils.keySet().iterator();
      while (iter.hasNext()) {
	String name = iter.next();
	View stencil = stencils.get(name);
	stencilPanel.addStencil(name, stencil);
      }
    }
    stencilPanel.activateView();
    return stencilPanel;
  }

  class ThumbnailListener extends MouseAdapter {

    public void mouseEntered(MouseEvent e) {
      if (debug) {
	System.out.println("ThumbnailListener.mouseEntered()");
      }

      view.showThumbNail(view.getThumbNail());
    }

    public void mouseExited(MouseEvent e) {
      if (debug) {
	System.out.println("ThumbnailListener.mouseExited()");
      }

      view.hideThumbNail();
    }

  }

  class ViewOptionListener implements ActionListener {
    public void actionPerformed(ActionEvent evt) { 
      System.out.println("ViewOptionListener.actionPerformed()");
      JButton src = (JButton) evt.getSource(); 

      if (popup != null && isShown) { 
	popup.hidePopup();
	isShown = false; 
      } else { 
	if (popup == null) {
	  popup = new JPanelPopup(false);
	  popup.setAlpha(0.7f);
	} else {
	  popup.removeAll();
	}
	Border b1 = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
	Border b2 = BorderFactory.createEmptyBorder(10, 10, 10, 10);
	Border b3 = BorderFactory.createEmptyBorder(4, 4, 4, 4);
	Border b4 = BorderFactory.createCompoundBorder(b1, b2);
	//BorderFactory.createLineBorder(Color.black));
	popup.setBorder(BorderFactory.createCompoundBorder(b3, b4));

	JComponent comp = null; 
	ViewOptions opt = doc.getViewOptions(); 
	if (opt != null) { 
	  opt.setView(getView());
	  comp = opt.getComponent();
	}
	if (comp == null) { 
	  popup.addComponent(new JLabel("This is view options"), BorderLayout.CENTER);
	} else {
	  popup.addComponent(comp, BorderLayout.CENTER);
	}
	//popup.setPreferredSize(new Dimension(300, 300)); 
	popup.pack();
	
	JComponent top = getAppComponent();
	Dimension dim = popup.getSize();
	Dimension tdim = top.getSize();
	Point p = src.getLocation(); 
	popup.setParentLocation(top, p.x, tdim.height - dim.height);
	popup.showPopup(top);
	isShown = true;
      }

    }

    JPanelPopup popup;
    boolean isShown = false; 
  }

  /*
  class ViewOptionListener extends MouseAdapter {

    public void mouseEntered(MouseEvent e) {
      if (true) {
	System.out.println("ViewOptionListener.mouseEntered()");
      }

      //view.showThumbNail(view.getThumbNail());
    }

    public void mouseExited(MouseEvent e) {
      if (true) {
	System.out.println("ViewOptionListener.mouseExited()");
      }

      //view.hideThumbNail();
    }

  }
  */

  class DocInfoListener extends MouseAdapter {

    JPanel info = null;
    JLabel mainLine, fileLine, typeLine, 
      titleLine, authorLine, orgLine, objectLine, createdLine, modifiedLine; 

    public void mouseEntered(MouseEvent e) {
      if (debug) {
	System.out.println("DocInfoListener.mouseEntered()");
      }

      if (info == null) {
	info = new JPanel();
	info.setOpaque(true);
	//info.setBackground(new Color(255, 255, 205, 64));
	info.setForeground(new Color(0, 0, 128));
	info.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
	//info.setFont(new Font("Serif", Font.PLAIN, 12));
	info.setLayout(new BoxLayout(info, BoxLayout.Y_AXIS));
	mainLine = new JLabel(getResourceString("Document.Info.text")); 
	mainLine.setOpaque(true);
	//mainLine.setBackground(new Color(0, 0, 0, 0));
	mainLine.setForeground(new Color(0, 0, 128));
	Font f = mainLine.getFont();
	mainLine.setFont(f.deriveFont(Font.BOLD, 12));
	info.add(mainLine);

	fileLine = new JLabel(); 
	info.add(fileLine);
	fileLine.setOpaque(true);
	//fileLine.setBackground(new Color(0, 0, 0, 0));

	typeLine = new JLabel(); 
	info.add(typeLine);
	typeLine.setOpaque(true);
	//typeLine.setBackground(new Color(0, 0, 0, 0));

	titleLine = new JLabel(); 
	info.add(titleLine);
	titleLine.setOpaque(true);
	//titleLine.setBackground(new Color(0, 0, 0, 0));

	authorLine = new JLabel(); 
	info.add(authorLine);
	authorLine.setOpaque(true);
	//authorLine.setBackground(new Color(0, 0, 0, 0));

	orgLine = new JLabel(); 
	info.add(orgLine);
	orgLine.setOpaque(true);
	//orgLine.setBackground(new Color(0, 0, 0, 0));

	objectLine = new JLabel(); 
	info.add(objectLine);
	objectLine.setOpaque(true);
	//objectLine.setBackground(new Color(0, 0, 0, 0));

	createdLine = new JLabel(); 
	info.add(createdLine);
	createdLine.setOpaque(true);
	//createdLine.setBackground(new Color(0, 0, 0, 0));

	modifiedLine = new JLabel(); 
	info.add(modifiedLine);
	modifiedLine.setOpaque(true);
	//modifiedLine.setBackground(new Color(0, 0, 0, 0));
      }
      
      fileLine.setText(getResourceString("Filename.text") + ": " + doc.getFilename());
      typeLine.setText(getResourceString("DiagramType.text") + ": " + 
		       Document.getDocumentTypeString(doc.getDocumentType()));
      String s = doc.getTitle();
      if (s == null) s = "";
      titleLine.setText(getResourceString("Title.text") + ": " + s);
      s = doc.getAuthor();
      if (s == null) s = "";
      authorLine.setText(getResourceString("Author.text") + ": " + s);
      s = doc.getOrganization();
      if (s == null) s = "";
      orgLine.setText(getResourceString("Organization.text") + ": " + s);
      objectLine.setText(getResourceString("Objects.text") + ": " + doc.count());
      long ctime = doc.getCreationTime();
      createdLine.setText(getResourceString("Create.text") + ": " + 
			  (new Date(ctime)).toString());
      long mtime = doc.getLastModificationTime();
      modifiedLine.setText(getResourceString("LastModified.text") + ": " + 
			   (new Date(mtime)).toString());

      view.showThumbNail(info);
    }

    public void mouseExited(MouseEvent e) {
      if (debug) {
	System.out.println("DocInfoListener.mouseExited()");
      }
      view.hideThumbNail();
    }

  }

  public static int getFiletype(String ext) {
    if (ext != null) {
      if (defaultFileExtension.equals(ext)) {
	return DEFAULT_FILE_TYPE;
      } else if (xmlFileExtension.equals(ext)) {
	return XML_FILE_TYPE;
      } else if (serializationFileExtension.equals(ext)) {
	return SERIALIZATION_FILE_TYPE;
      } else if (stencilFileExtension.equals(ext)) {
	return XML_STENCIL_FILE_TYPE;
      }
    }
    return UNKNOWN_FILE_TYPE;
  }

  public static String getExtension(int filetype) {
    if (filetype == DEFAULT_FILE_TYPE) {
      return defaultFileExtension;
    } else if (filetype == XML_FILE_TYPE) {
      return xmlFileExtension;
    } else if (filetype == SERIALIZATION_FILE_TYPE) {
      return serializationFileExtension;
    } else if (filetype == XML_STENCIL_FILE_TYPE) {
      return stencilFileExtension;
    }
    return null;
  }

  public String getDocumentExtension(Document.DocumentType type) {
    return defaultFileExtension;
  }

  public void setFrameTitle() {
    String title = getResourceString("Title");

    ProjectItem item = null;
    if (projectManager != null) {
      item = projectManager.getCurrentItem();
    }
    if (item != null) {
      String name = item.getFullName();
      String itemType = item.getType();
      if (itemType != null) { 
	title += (" [" + name + " -- " + itemType + "]");
      } else {
	title += (" [" + name + "]");
      }
    } else if (currentFilename != null) {
      title += (" [" + currentFilename + "]");
    } else {
      title += " [Untitled]";
    }
    frame.setTitle(title);
  }

  protected void setCurrentFilename(String filename) {
    currentFilename = filename;
    setFrameTitle();
  }

  protected String getCurrentFilename() {
    return currentFilename;
  }

  protected String getCurrentProjectFilename() {
    if (projectManager != null) {
      Project project = projectManager.getCurrentProject();
      return project.getProjectFilename();
    }
    return null;
  }

  public void reportOpenFileError(String filename, String message) { 
    String msg1 = "Load file failed: " + filename;
    displayMessage(msg1);
    JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + message,
				  "Open File Error", 
				  JOptionPane.ERROR_MESSAGE);
  }

  public void reportOpenProjectError(String filename, String message) { 
    String msg1 = "Load project failed: " + filename;
    displayMessage(msg1);
    JOptionPane.showMessageDialog(frame.getJFrame(), msg1 + "\n" + message,
				  "Load Project Error", 
				  JOptionPane.ERROR_MESSAGE);
  }

  public static final String defaultFileExtension = "dml";

  public static final String serializationFileExtension = "dse";

  public static final String xmlFileExtension = "xml";

  public static final String stencilFileExtension = "sml";

  public static final String defaultFileDescription = "Draw files (*.dml)";

  public static final String serializationFileDescription = "Draw serialization files (*.dse)";

  public static final String xmlFileDescription = "XML files (*.xml)";

  public static final String stencilFileDescription = "Visual Modeler stencil files (*.sml)";

  public static final ExtensionFileFilter defaultFileFilter = 
    new ExtensionFileFilter(defaultFileExtension, 
			    defaultFileDescription, 
			    DEFAULT_FILE_TYPE);

  public static final ExtensionFileFilter serializationFileFilter = 
    new ExtensionFileFilter(serializationFileExtension, 
			    serializationFileDescription,
			    SERIALIZATION_FILE_TYPE);

  public static final ExtensionFileFilter xmlFileFilter = 
    new ExtensionFileFilter(xmlFileExtension, 
			    xmlFileDescription, 
			    XML_FILE_TYPE);

  public static final ExtensionFileFilter projectFileFilter = 
    new ExtensionFileFilter(Project.PROJECT_FILE_SUFFIX, 
			    Project.PROJECT_FILE_DESCRIPTION);

  public static final ExtensionFileFilter projectArchiveFileFilter = 
    new ExtensionFileFilter(Project.PROJECT_ARCHIVE_SUFFIX, 
			    Project.PROJECT_ARCHIVE_DESCRIPTION);

  protected static final FileFilter[] defaultFileFilters = { defaultFileFilter };

  protected static final FileFilter[] testFileFilters = { xmlFileFilter,
      serializationFileFilter };

  protected FileFilter[] getSupportedFileFilters() {
    return defaultFileFilters;
  }

  protected UserProfile userProfile; 

  protected SimpleDrawDocument doc;
  protected SimpleDrawView view;

  protected int canvasWidth = 1000; // default initial width
  protected int canvasHeight = 800; // default initial height

  protected boolean showRuler = true;

  protected DocumentChangeListener docChangeListener = null;

  protected DrawShapePropertyPane propPane = null;
  protected ModelPropertiesPane   modelPropPane = null;
  protected TabbedAttributePane   tabbedPropPane = null; 

  protected ProjectListener projectListener = null;
  protected ProjectPane projectPane = null;
  protected TabbedProjectPanel tabbedProjectPanel; 

  protected TemplatePanel templatePanel = null;
  protected StencilPanel stencilPanel = null;

  protected ZoomControl zoomControl; 
  protected boolean useZoomSlider; 
  protected boolean showViewOptions; 

  protected boolean useStandardFontsOnly = false;

  protected boolean useTabbedPane = false;

  protected static Map<String, DrawAction> actions = new HashMap<String, DrawAction>();

  protected Map<String, ButtonGroup> groups = new HashMap<String, ButtonGroup>();
  protected Map<String, JComponent> toolbars = new HashMap<String, JComponent>();
  protected Map<String, JMenuItem> menus = new HashMap<String, JMenuItem>();
  protected java.util.List<InputDevice> devices = new ArrayList<InputDevice>(8);
  protected java.util.List<ToolPanel> templatePanels = new ArrayList<ToolPanel>(8);
  protected Map<String, Template> templates = new HashMap<String, Template>();
  protected Map<String, View> stencils = new HashMap<String, View>();
  protected Map<String, Encoder> encoderMap = new HashMap<String, Encoder>();
  protected Map<String, PlugIn> plugInMap = new HashMap<String, PlugIn>();

  protected String currentFilename = null;
  protected int currentFiletype = DEFAULT_FILE_TYPE; // UNKNOWN_FILE_TYPE;

  protected EditManager editManager;
  protected UndoChangeListener undoChangeListener;

  protected ProjectManager projectManager = null;

  protected HoverPopupManager hoverPopupManager = null;

  protected DocumentRunnerManager runnerManager = null; 

  protected DrawAttr drawattr = new DrawAttr();;
  protected ArrowAttr headArrowAttr = new ArrowAttr();
  protected ArrowAttr tailArrowAttr = new ArrowAttr();

  protected JMenu recentlyOpenedFilesMenu;

  protected OpenRecentFileListener recentlyOpenedFilesAction;

  protected static Map<String, FileEntry> recentlyOpenedFiles = new HashMap<String, FileEntry>(); // shortname -> entry

  protected static int untitledCount = 1;

  public static final String STENCIL_DIR = "stencils";

  protected static String identification;

  protected static String prefix;

  public static final boolean debug = false;

  public static final boolean testing = false;

  protected void initTabbedPaneUI(JTabbedPane tabbedPane) {
    if (tabbedPane != null) { 
      LookAndFeelManager lnfManager = LookAndFeelManager.getInstance();
      if (lnfManager.isSubstanceLnF()) { 
	tabbedPane.putClientProperty(org.jvnet.substance.SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY,
				     Boolean.TRUE);
	/*
	tabbedPane.putClientProperty(
	     org.jvnet.substance.SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION,
	     Boolean.TRUE);
	*/

	org.jvnet.substance.api.tabbed.VetoableTabCloseListener tabCloseListener = 
	  new org.jvnet.substance.api.tabbed.VetoableTabCloseListener() { 
	    public void tabClosing(JTabbedPane tabbedPane, Component tabComponent) {	      
	      //System.out.println("VetoableTabCloseListener.tabClosing()"); 
	    }	    

	    public void tabClosed(JTabbedPane tabbedPane, Component tabComponent) {
	      //System.out.println("VetoableTabCloseListener.tabClosed()"); 
	    }

	    public boolean vetoTabClosing(JTabbedPane tabbedPane,Component tabComponent) {
	      //System.out.println("VetoableTabCloseListener.vetoTabClosing()"); 
	      
	      if (tabbedPane.getTabCount() == 1) {
		return true;
	      }

	      if (tabComponent instanceof SimpleDrawView) { 
		if (!closeTabView((SimpleDrawView) tabComponent)) { 
		  return true;
		}
	      }
	      return false; 
	    }
	  };

	org.jvnet.substance.SubstanceLookAndFeel.registerTabCloseChangeListener(tabbedPane, tabCloseListener);

	org.jvnet.lafwidget.tabbed.DefaultTabPreviewPainter previewPainter =
	  new org.jvnet.lafwidget.tabbed.DefaultTabPreviewPainter() {
	    public void previewTab(JTabbedPane tabPane, int tabIndex, Graphics g,
				   int x, int y, int w, int h) {
	      Component tabComponent = tabPane.getComponentAt(tabIndex);
	      if (tabComponent == null) {
		return;
	      // if (!tabComponent.isShowing())
	      // return;
	      }

	      int compWidth = tabComponent.getWidth();
	      int compHeight = tabComponent.getHeight();

	      Graphics2D g2 = (Graphics2D) g.create();
	      if (!tabPane.isEnabledAt(tabIndex)) {
		g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
	      }
	      //g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));

	      if (tabComponent instanceof View) { 
		View view = (View) tabComponent;
		BufferedImage tempCanvas = new BufferedImage(w, h,
							     BufferedImage.TYPE_INT_ARGB);

		DrawCanvas thumbnail = view.createThumbNail(w, h);
		Graphics tempCanvasGraphics = tempCanvas.getGraphics();
		thumbnail.paintComponent(tempCanvasGraphics);		
		g2.drawImage(tempCanvas, 0, 0, null);
	      } else { 	      	      
		if ((compWidth > 0) && (compHeight > 0)) {
		  // check if need to scale down
		  float coef = Math.min((float) w / (float) compWidth, 
					(float) h / (float) compHeight);
		  BufferedImage tempCanvas = new BufferedImage(compWidth, compHeight,
							       BufferedImage.TYPE_INT_ARGB);
		  // draw tab component
		  Graphics tempCanvasGraphics = tempCanvas.getGraphics();
		  tabComponent.paint(tempCanvasGraphics);				  
		  if (coef < 1.0) {
		    int sdWidth = (int) (coef * compWidth);
		    int sdHeight = (int) (coef * compHeight);
		    int dx = (w - sdWidth) / 2;
		    int dy = (h - sdHeight) / 2;
		    
		    g2.drawImage(org.jvnet.lafwidget.LafWidgetUtilities
				 .createThumbnail(tempCanvas, sdWidth), dx, dy, null);
		    
		  } else {
		    g2.drawImage(tempCanvas, 0, 0, null);
		  }

		}
	      }	 
	      g2.dispose();     
	    }
	  };

	tabbedPane.putClientProperty(org.jvnet.lafwidget.LafWidget.TABBED_PANE_PREVIEW_PAINTER,
				     previewPainter); 
      }
    }
  }  

  private Animator blinker; 
  
  private void startBlinker() { 
    blinker = new Animator(500,
      new AnimationListener() { 
	public void doAction() { 
	  if (doc != null && view != null) { 
	    DrawShape s = doc.getSelectedShape();
	    if (s instanceof TextShape) { 
	      DrawCanvas canvas = getCanvas();
	      //canvas.repaintShape(s, true);
	    }
	  }
	}
      });
  }

  public void stopThreads() {
    //System.out.println("xj.graph2d.app.DrawApp.stopThreads()");
    logInfo("Stop all threads.");

    if (blinker != null) { 
      blinker.interrupt();
      blinker = null;
    }

    runnerManager.stopAllRunners();
  }

  public static void main(String[] args) {
    startApp(args, "resources.graph2d.app.DrawApp");

    try { 
      SwingUtilities.invokeAndWait(new Runnable() {
	  public void run() {
	    initGUI();

	    createSplashScreen("/icons/splashDraw.jpg");
	    setSplashScreenProgress(20);

	    BaseApplicationFrame frame = new BaseApplicationFrame();
	    DrawApp app = new DrawApp(frame);
	    frame.setApplication(app);
	    setSplashScreenProgress(100);
	    frame.setFrame();
	    hideSplashScreen();
	  }
	});
    } catch (Exception e) {       
    }

  }


}

// ////////////////////////////////////////////////////////
// 
// Helper Classes
//
// ///////////////////////////////////////////////////////

class FileEntry {

  FileEntry(String filename, int filetype) {
    this.filename = filename;
    this.filetype = filetype;
  }

  String filename;

  int filetype;

}
